/*
-----------------------------------------------------------------------------
Filename:    Simulator.cpp
-----------------------------------------------------------------------------
*/

#include "Simulator.h"

using namespace Ogre;
using namespace OgreBites;

//-------------------------------------------------------------------------------------
Simulator::Simulator(void)
{
	mTerrainPos = Ogre::Vector3(0, 0, 0);
	PhysXDebug = false;
	bTerrainLoaded = false;
	bSceneSetUp = false;
	bPredictionModuleConnected = false;
	nCameraSpeed = 100;
	
	extern logwriter logFile;
	logFile.writeEntry("log_start");


	mPhysXWorld = new PhysXWorld();
	mMeshManager = NxOgre::MeshManager::getSingleton();
}
//-------------------------------------------------------------------------------------
Simulator::~Simulator(void)
{
	if(bSceneSetUp)
	{
		delete mMap;
	}

	extern logwriter logFile;
	logFile.close();
	extern AppSettings settings;
	settings.saveSettings();
}

bool Simulator::connectToPredictionModule(void)
{
	extern AppSettings settings;

	std::cout << "\n<Simulator>: connecting to prediction module (" << settings.applicationSettings.sServerAddress.data() << ":" << settings.applicationSettings.nPort << ") ...";
	bool online = connectToServer(settings.applicationSettings.sServerAddress,settings.applicationSettings.nPort);

	if(online)
	{
		bPredictionModuleConnected = true;
	}
	else
	{
		bPredictionModuleConnected = false;
	}

	return online;
}

//-------------------------------------------------------------------------------------

Ogre::String Simulator::getSceneDescriptionFromPredictionModule(void)
{
	if(!bPredictionModuleConnected)
	{
		return "";
	}
	else
	{
		std::cout << "\n<Simulator>: getting scene...";
		sendMessageToServer("getScene");
		Ogre::String data = receiveMessageFromServer();
		return data;
	}
}

//-------------------------------------------------------------------------------------

void Simulator::getUpdateFromPredictionModule(void)
{
	if(bPredictionModuleConnected)
	{
		std::cout << "\n<Simulator>: getting update from server...";
		sendMessageToServer("getUpdate");
		Ogre::String data = receiveMessageFromServer();
		processUpdate(data);
	}
}

//-------------------------------------------------------------------------------------

void Simulator::processUpdate(Ogre::String data)
{
	TiXmlDocument xmlDoc;

	xmlDoc.Parse(data.data());

	TiXmlElement *root = xmlDoc.FirstChildElement();

	if(root!=0)
	{
		TiXmlElement *startRain = root->FirstChildElement("StartRain");

		if(startRain != 0)
		{
			mWeatherSystem->createRain();
		}

		TiXmlElement *fireAllEmitters = root->FirstChildElement("FireAllEmitters");

		if(fireAllEmitters != 0)
		{
			mPhysXWorld->fireAllEmitters();
		}
	}
}

//-------------------------------------------------------------------------------------

void Simulator::createScene(void)
{
		
	    mSceneImporter.importSceneDBFromXml("SaveFiles/SaveDB.xml");
		mGui->mMainMenu->show();

		Ogre::StringVector saveNames;
		int count = mSceneImporter.saveFiles.size();
		for(int i=0;i<count;i++)
		{
			saveNames.push_back(mSceneImporter.saveFiles.at(i)->getName());
		}

		mGui->mMainMenu->injectMenuItems(saveNames);
		mGui->setGuimode(true);
}

//-------------------------------------------------------------------------------------

void Simulator::buildWorld(Ogre::String saveFileName)
{
	extern AppSettings settings;

	if(settings.applicationSettings.bUseShadows)
	{
		// set shadow properties
		mSceneMgr->setShadowTechnique(SHADOWTYPE_TEXTURE_MODULATIVE);
		mSceneMgr->setShadowColour(ColourValue(0.5, 0.5, 0.5));
		mSceneMgr->setShadowTextureSize(1024);
		mSceneMgr->setShadowTextureCount(1);
	}
  
	mCamera->setPosition(Ogre::Vector3(-45.1056, 32.243, -49.2175));
	mCamera->setOrientation(Ogre::Quaternion(-0.720312,0.123759, 0.672868, 0.115734));
	mCamera->setNearClipDistance(0.5);
	mCamera->setFarClipDistance(50000);
	mCameraMan->setTopSpeed(nCameraSpeed);

	mGui->setGuimode(false);

	if (mRoot->getRenderSystem()->getCapabilities()->hasCapability(Ogre::RSC_INFINITE_FAR_PLANE))
    {
        mCamera->setFarClipDistance(0);   // enable infinite far clip distance if we can
    }

	mMap = new Map(mSceneMgr,"terrain_empty.png");
	mWeatherSystem = new WeatherSystem(mSceneMgr, mMap);

	if(bPredictionModuleConnected)
	{
		mSceneImporter.importSaveDataFromXmlString(getSceneDescriptionFromPredictionModule());
	}
	else
	{
		mSceneImporter.importSaveDataFromXmlFile(saveFileName);
	}
	mSceneImporter.importScene(mPredefinedCameraDescriptions,mSceneMgr,mMap,mWeatherSystem);

	int cameraCount = mPredefinedCameraDescriptions->lCameraDescriptions.size();
	Ogre::StringVector cameraNames;
	for(int i=0;i<cameraCount;i++)
	{
		cameraNames.push_back(mPredefinedCameraDescriptions->lCameraDescriptions.at(i)->getDescription());
	}
	mGui->injectCameras(cameraNames);

	bSceneSetUp = true;
    setupPhysics();

	mSceneImporter.importActors(mRenderSystem,mSceneMgr,mScene,mPhysXWorld);

	printf("\n%s","--------------------------------------------------");
	printf("\n%s","Scene loaded...");
	printf("\n%s","--------------------------------------------------");
}

//-------------------------------------------------------------------------------------

void Simulator::setupPhysics(void)
{
	extern AppSettings settings;

	if(bSceneSetUp)
	{
		  // Create the world.
	  mWorld = NxOgre::World::createWorld();
  
	  NxOgre::ResourceSystem::getSingleton()->openProtocol(new Critter::OgreResourceProtocol());

	  mWorld->getRemoteDebugger()->connect();

	  // Create the scene
	  NxOgre::SceneDescription scene_description;
	  scene_description.mGravity = NxOgre::Constants::MEAN_EARTH_GRAVITY;
	  if(settings.applicationSettings.bUseHardwarePhysX)
	  {
			 scene_description.mUseHardware = true;
	  }
	  else
	  {
		     scene_description.mUseHardware = false;
	  }
	  scene_description.mWorkerThreadsCount = 2;
	  scene_description.mBackgroundThreadsCount = 2;
	  scene_description.mFlags |= NxOgre::SceneFlags::EnableMultithread | NxOgre::SceneFlags::FluidPerformanceHint | NxOgre::SceneFlags::AlternativeFluidTriangleCollision;


	  mScene = mWorld->createScene(scene_description);
  
	  // Set default material properties
	  mDefaultMaterial = mScene->getMaterial(0);
	  mDefaultMaterial->setRestitution(0.1f);
	  mDefaultMaterial->setDynamicFriction(0.9);
	  mDefaultMaterial->setStaticFriction(0.5);
  
	  // Plane creation
	  mScene->createSceneGeometry(NxOgre::PlaneGeometryDescription());

	  // Create the rendersystem.
	  mRenderSystem = new Critter::RenderSystem(mScene, mSceneMgr);

		NxOgre::SceneGeometry * sg;

		Ogre::TerrainGroup::TerrainIterator ti = mMap->getTerrainGroup()->getTerrainIterator();
		while (ti.hasMoreElements())
		{
			Terrain * t = ti.getNext()->instance;
			sg = mRenderSystem->createTerrain(t);
		}
  

		printf("\n%s","--------------------------------------------------");
		printf("\n%s","Physical world set up...");
		printf("\n%s","--------------------------------------------------");
	}
}

//-------------------------------------------------------------------------------------

void Simulator::resetPhysics(void)
{
	if(bSceneSetUp)
	{
		mPhysXWorld->clearAll();
		delete mRenderSystem;
		NxOgre::World::destroyWorld();
		setupPhysics();
	}
}

//-------------------------------------------------------------------------------------

void Simulator::advancePhysX(const Ogre::FrameEvent& evt)
{
	if(bSceneSetUp)
	{
	  mWorld->advance(evt.timeSinceLastFrame);
	  mLastTimeStep = mScene->getTimeStep().getModified();
	}
}

//-------------------------------------------------------------------------------------

void Simulator:: correctPosition(void)
{
	if(bSceneSetUp)
	{
	//don't let the camera go under the terrain

			Vector3 camPos = mCamera->getPosition();
			Ray ray;
			ray.setOrigin(Vector3(camPos.x, mTerrainPos.y + 10000, camPos.z));
			ray.setDirection(Vector3::NEGATIVE_UNIT_Y);

			TerrainGroup::RayResult rayResult = mMap->getTerrainGroup()->rayIntersects(ray);
			Real distanceAboveTerrain = 1;
			Real fallSpeed = 300;
			Real newy = camPos.y;
			if (rayResult.hit)
			{
				if (camPos.y < rayResult.position.y + distanceAboveTerrain)
				{
					newy = std::max(rayResult.position.y + distanceAboveTerrain, newy);
					mCamera->setPosition(camPos.x, newy, camPos.z);
				}
				
			}
	}
}
//-------------------------------------------------------------------------------------

bool Simulator::frameRenderingQueued(const Ogre::FrameEvent& evt)
{
	bool ret = BaseEngine::frameRenderingQueued(evt);

	if(bSceneSetUp)
	{

	  advancePhysX(evt);
	  correctPosition();

	  if(!bTerrainLoaded)
	  {
		  if (mMap->getTerrainGroup()->isDerivedDataUpdateInProgress())
		  {
			  mTrayMgr->moveWidgetToTray(mDetailsPanel, OgreBites::TL_NONE, 0);
			  mDetailsPanel->hide();

			  mGui->showLoadingLabel("Updating scene, please wait...");
		  }
		  else
		  {
			  mGui->hideLoadingLabel();

			  //mTrayMgr->moveWidgetToTray(mDetailsPanel, OgreBites::TL_TOPRIGHT, 0);
			  //mDetailsPanel->show();
			  bTerrainLoaded = true;
		  }
	  }

	}
    return ret;
}
//-------------------------------------------------------------------------------------
bool  Simulator::keyPressed( const OIS::KeyEvent &arg )
{
	if(bSceneSetUp)
	{
		if(bTerrainLoaded)
		{
				BaseEngine::keyPressed(arg);
			
				if(arg.key == OIS::KC_F9)
				{
					float mapX = mTerrainPos.x;
					float mapY = mTerrainPos.y;
					float mapZ = mTerrainPos.z;

					float camX = mCamera->getPosition().x;
					float camY = mCamera->getPosition().y;
					float camZ = mCamera->getPosition().z;

					float camoW =  mCamera->getOrientation().w;
					float camoX =  mCamera->getOrientation().x;
					float camoY =  mCamera->getOrientation().y;
					float camoZ =  mCamera->getOrientation().z;
				
					extern logwriter logFile;
					logFile.writeEntry("Map position:", mapX , mapY, mapZ);
					logFile.writeEntry("Camera position:", camX , camY, camZ);
					logFile.writeEntry("Camera orientation:", camoW, camoX , camoY, camoZ);
				
				}
				else if(arg.key == OIS::KC_6)
				{
					Ogre::SceneNode *fix = mSceneMgr->getRootSceneNode()->createChildSceneNode();
					fix->setPosition(0,100,0);

					std::vector<Ogre::Vector3> path;
					path.push_back(Ogre::Vector3(200, 0, 0));
					path.push_back(Ogre::Vector3(0, -50, 100));
					path.push_back(Ogre::Vector3(-500, 100, 0));
					path.push_back(Ogre::Vector3(0, 200, -300));
					path.push_back(Ogre::Vector3(200, 0, 0));

					cameraFollowPath(fix,path);
				}
				else if(arg.key == OIS::KC_7)
				{
					this->giveCameraControlToPlayer();
				}
				else if(arg.key == OIS::KC_V)
				{
				   if(!PhysXDebug)
				   {
					   mRenderSystem->setVisualisationMode(NxOgre::Enums::VisualDebugger_ShowAll);
					   PhysXDebug = true;
				   }
				   else
				   {
					   mRenderSystem->setVisualisationMode(NxOgre::Enums::VisualDebugger_ShowNone);
					   PhysXDebug = false;
				   }
	   
				}
				else if (arg.key == OIS::KC_H)
				{
					mMap->initBlendMaps();
				}
				else if (arg.key == OIS::KC_F12)
				{


				}
				else if (arg.key == OIS::KC_F10)
				{
					resetBenchMarkData();
				}
				else if (arg.key == OIS::KC_F11)
				{
					this->writeBenchMarkResults("stats.xml");
				}
				else if (arg.key == OIS::KC_I)
				{

				}
				else if (arg.key == OIS::KC_K)
				{

				}
				else if (arg.key == OIS::KC_J)
				{

				}
				else if (arg.key == OIS::KC_L)
				{

				}
				else if (arg.key == OIS::KC_U)
				{

				}
				else if (arg.key == OIS::KC_O)
				{

				}
				else if (arg.key == OIS::KC_NUMPAD2)
				{

				}
				else if (arg.key == OIS::KC_NUMPAD8)
				{

				}
				else if (arg.key == OIS::KC_NUMPAD4)
				{

				}
				else if (arg.key == OIS::KC_NUMPAD6)
				{

				}
				else if (arg.key == OIS::KC_DELETE)
				{

				}
  
		}
	}
	return true;
}
//-------------------------------------------------------------------------------------
bool Simulator::mousePressed( const OIS::MouseEvent &arg, OIS::MouseButtonID id )
{
	
	bool ret = BaseEngine::mousePressed(arg,id);

	if(bSceneSetUp)
	{
			int nRightMouse = 1;
			int nMiddleMouse = 2;

			if(id == nMiddleMouse)
			{
				mGui->toggleGuimode();
			}
			else if(id == nRightMouse)
			{
		

			}	
	}
	else
	{
		//we are in the main menu
	}

	return ret;
}
//-------------------------------------------------------------------------------------
bool Simulator::mouseMoved( const OIS::MouseEvent &arg )
{
	bool ret = false;

	if(bSceneSetUp)
	{
		if(bTerrainLoaded)
		{
			ret = BaseEngine::mouseMoved(arg);
		}
	}
	else
	{
		//we are in the main menu
		ret = BaseEngine::mouseMoved(arg);
	}

	return ret;
}
//-------------------------------------------------------------------------------------
void Simulator::itemSelected(SelectMenu* menu)
{
	if(bSceneSetUp)
	{
		if(menu->getName() == "TCameraMenu")
		{
			int selection = menu->getSelectionIndex();
			
			if(selection > 0)
			{
				if( ( (selection-1)>=0 ) && ((selection-1) < mPredefinedCameraDescriptions->lCameraDescriptions.size()) )
				{

					CameraDescription *camDesc = mPredefinedCameraDescriptions->lCameraDescriptions.at(selection-1);

					mCamera->setPosition(camDesc->getPosition());
					mCamera->setOrientation(camDesc->getOrientation());
				}
			}
			
		}
	}
	else
	{
	   //We are in the main menu

		if(menu->getName() == "TMainMenu")
		{
			int selection = menu->getSelectionIndex();
			mGui->mMainMenu->setDescriptionText(mSceneImporter.saveFiles.at(selection)->getDescription());
		}

		}
}
//-------------------------------------------------------------------------------------
void Simulator::buttonHit(Button* button)
{
	if(bSceneSetUp)
	{

		if(  (button->getName() == "TUpdateButton") )
		{
			mGui->showLoadingLabel("Syncing scene..");
			getUpdateFromPredictionModule();
			mGui->hideLoadingLabel();
		}
	}
	else //we are in the mein menu
	{
		if(  (button->getName() == "TExitButton") )
		{
			this->mShutDown = true;
		}
		else if(  (button->getName() == "TStartButton") )
		{
			mGui->mMainMenu->hide();
			buildWorld(mSceneImporter.saveFiles.at(mGui->mMainMenu->getSelectionIndex())->getFileName());
		}
		else if(  (button->getName() == "TConnectButton") )
		{
			if(!connectToPredictionModule())
			{
				mTrayMgr->showOkDialog("Error","Cannot connect to the prediction module!");
			}
			else
			{
				mGui->mMainMenu->hide();
				buildWorld("");
			}

		}

	}
}
//-------------------------------------------------------------------------------------
void Simulator::createFrameListener(void)
{
    BaseEngine::createFrameListener();

    mGui = new SimulatorGui(mCameraMan, mTrayMgr);
}
//-------------------------------------------------------------------------------------
void Simulator::loadResources(void)
{
    BaseEngine::loadResources();
}
//-------------------------------------------------------------------------------------

void Simulator::writeBenchMarkResults(Ogre::String fileName)
{
	extern AppSettings settings;
	logwriter *logFile = new logwriter(fileName,"BenchmarkStats",true);

	Ogre::String fpsData = this->getBenchMarkData();

	int width = mWindow->getWidth();
	int height = mWindow->getHeight();
	int depth = mWindow->getColourDepth();
	int FSAA = mWindow->getFSAA();
	bool fullScreen = mWindow->isFullScreen();
	bool physx;
	bool shadows = settings.applicationSettings.bUseShadows;

	if(settings.applicationSettings.bUseHardwarePhysX)
	{
		physx  = mWorld->hasHardware();
	}
	else
	{
		physx = false;
	}
	
	Ogre::String videoMode = Ogre::StringConverter::toString(width).append(" x ").append(Ogre::StringConverter::toString(height).append(" @ ").append(Ogre::StringConverter::toString(depth)).append(" bit color depth"));
	Ogre::String antialiaSing = Ogre::StringConverter::toString(FSAA);
	Ogre::String fScreen = Ogre::StringConverter::toString(fullScreen);
	Ogre::String physxHardware =  Ogre::StringConverter::toString(physx);
	Ogre::String dynamicShadows =  Ogre::StringConverter::toString(shadows);


	logFile->writeEntry("Video Mode",videoMode.data());
	logFile->writeEntry("Full screen",fScreen.data());
	logFile->writeEntry("Antialiasing",antialiaSing.data());
	logFile->writeEntry("Dynamic shadows enabled",dynamicShadows.data());
	logFile->writeEntry("PhysX enabled",physxHardware.data());
	logFile->writeEntry("Frame stats",fpsData.data());
	logFile->close();

}

//-------------------------------------------------------------------------------------